                    EJECT
*******************************************************************************
* BoxCtl CDEFProc
*
* (C)  Copyright Apple Computer, Inc. 1988
* All rights reserved.
*
* by Keith Rollin
* July 8, 1988
*
* This is a Custom Control for the Apple IIGS. It is similar in appearance
* and action to the 'resize' box you get when clicking on an object in GS Draw
* or MacDraw.
*
* The Control is essentially a frame with 4 or 8 grow knobs on the corners
* and/or edges. Dragging on any of these knobs will grow the control. Dragging
* on the frame will move the entire control (like moving a window). A flag can
* be set so that the control will be dragged if the user clicks in the interior
* of the control as well. Part codes for the knobs and frame are listed below.
*
* Snapping the rectangle's coordinates to a grid is also supported. The
* rectangle is snapped to the nearest point on the grid. This effectively means
* that I perform rounding, and not truncating.
*
* The frame of the control is the rectangle passed to NewControl. The size of
* the knobs and the size of the grid are passed in ctlValue. The 'interior drag
* flag' is passed in ctlFlags. See below for details.
*
* Color is supported. Currently, only two colors are used: one for the frame,
* and one for the knobs. The default colors are black.
*
*
*
*
* Modification History:
*
*   v1.0a1  02-Apr-88   kaar    New Today
*   v1.0a2  04-Apr-88   kaar    Changed part codes. Defined CtlData to point
*                               to a data block. Added edge knobs. Grid can
*                               now be rectangular. Knob rectangles no longer
*                               part of control (created on the fly). Added
*                               new flags to ctlFlag. CtlValue now contains
*                               only the size of the knobs.
*   v1.0a3  07-Apr-88   kaar    Removed dependance on Direct Page from the
*                               application (now uses the stack). Responds to
*                               Hilite(255).
*   V1.0a4  08-Apr-88   kaar    Added support for when Param1/2 are null.
*   V1.0B1  23-Apr-88   kaar    Fixed some bugs. Checked for gridsizes of 0
*                               and 1. Called InvalRect in DragCtl.
*   V2.0B2  08-Jun-88   kaar    Cleaned up the code some in response to a
*                               code review. Version number bumped to 2.0 to
*                               reflect existance of simpler 1.0 version for
*                               a technote. Added support for fCtlTie in the
*                               owner window's frame flags. Improved perfor-
*                               mance and legibility of Snap2Grid routine.
*   V2.0B3  08-Jul-88   kaar    Part codes changed again. It seems that only
*                               one indicator per control is allowed. Turned
*                               the source code upside down and put it into
*                               Steve Glass format. Implemented some of Dan's
*                               programming style suggestions.
*
* Version 1.0       kaar
*
*   Used in Custom Control technote
*
*
* Version 2.0       kaar
*
*   Release for Source Code Demo Disk #1
*
*
*******************************************************************************
*
*   Part Code returned:
*
*           $A0 for all parts of the control (all parts are 'indicators')
*
*
*   CtlValue:   Bits  8-15: width of grow knobs
*               Bits  0- 7: height of grow knobs
*
*   CtlFlag bits:
*               7 = 1 - Control is invisible
*                 = 0 - Control is visible
*               2 = 1 - Has edge knobs
*                 = 0 - Doesn't
*               1 = 1 - Has corner knobs
*                 = 0 - Doesn't
*               0 = 1 - Clicking in interior will drag control
*                 = 0 - Clicking in interior does nothing
*
*   Data:   Contains a pointer to the following data block. This data
*           block holds the following information, which is copied to
*           the end of the control record:
*
*               WORD: Min Y when growing
*               WORD: Min X when growing
*               WORD: Max Y when growing
*               WORD: Max X when growing
*               WORD: Spacing for Y segment of grid
*               WORD: Spacing for X segment of grid
*
*   Color Table:
*       WORD: Bits  12-15:  <reserved>
*                    8-11:  Knob Color
*                    4- 7:  Inactive Frame/Knob Color
*                    0- 3:  Frame Color
*
*******************************************************************************
*
*   How to use the Control:
*
*       Initialization and tracking of the control work just like any other;
*       set it up with NewControl. When GetNextEvent returns inContent, call
*       FindControl to see if you hit the control. If you did, call
*       TrackControl.
*
*   Initialization:
*
*               pha                     ; space for result
*               pha
*               PushLong theWindow
*               PushLong #theRect       ; pointer to bounding rectangle
*               PushLong #0             ; no title
*               PushWord #%00000111     ; vis, int will drag, corners & edges
*               PushWord #$0503         ; Width = 5/Height = 3
*               PushLong #CtrlData      ; Pointer to additional data
*               PushLong #BoxProc       ; pointer to DefProc
*               PushLong #0             ; refcon
*               PushLong #0             ; use std color table
*               _NewControl
*               PullLong theControl
*
*   theRect     dc i2'50,140,110,300'
*   CtrlData    dc i2'10'               ; min Y
*               dc i2'10'               ; min X
*               dc i2'100'              ; max Y
*               dc i2'200'              ; max X
*               dc i2'16'               ; grid Y
*               dc i2'32'               ; grid X
*
*******************************************************************************
;-------------------------------------------
;
; --- Constants
;
; --- Stack Frame/Local Direct Page usage
;

OrigD               gequ 1               ; caller's saved direct page register
OrigB               gequ OrigD+2         ; caller's saved data bank register
work                gequ OrigB+1         ; general use work space
CtlPtr              gequ work+4          ; pointer to control record
RtnAddr             gequ CtlPtr+4        ; RTL address back to Control Manager
theCtlHandle        gequ RtnAddr+3       ; handle to control record
CtlParam            gequ theCtlHandle+4  ; add'l parameter passed to DefProc
CtlCode             gequ CtlParam+4      ; operation to perform
ReturnValue         gequ CtlCode+2       ; space for return value to Ctl Mgr.

;
; --- Offsets for my control record
;

oCtlMinY            gequ oCtlColor+4
oCtlMinX            gequ oCtlMinY+2
oCtlMaxY            gequ oCtlMinX+2
oCtlMaxX            gequ oCtlMaxY+2
oCtlGridY           gequ oCtlMaxX+2
oCtlGridX           gequ oCtlGridY+2
oCtlSize            gequ oCtlGridX+2

;
; --- Part codes for my control
;

BoxCtlPart          gequ $A0             ; This is returned to the app.

FramePart           gequ $01             ; These are used internally to tell
InteriorPart        gequ $02             ; 'myDragCtl' what part of the
ULPart              gequ $03             ; control we are dragging.
TopPart             gequ $04
URPart              gequ $05
RightPart           gequ $06
LLPart              gequ $07
BottomPart          gequ $08
LRPart              gequ $09
LeftPart            gequ $0A


;
; --- Masks for ctlFlags
;

dragIntMask         gequ %00000001       ; if set, let user drag on interior
knobCornerMask      gequ %00000010       ; if set, draw knobs on the corners
knobEdgeMask        gequ %00000100       ; if set, draw knobs on the edges
visMask             gequ ctlInVis

                    EJECT
*******************************************************************************
*
CtlData             data
*
* Description:      Storage for CDEF
*
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************

deltaX              ds 2                ; used when growing the control
deltaY              ds 2
ColorPtr            ds 4                ; pointer to actual color table to use
theParam            ds 4                ; copy of CtlParam passed on stack
dragPart            ds 2                ; internal part of control hit

myCtlRect           ds 8                ; copy of CtlRect in control record
myCtlGridY          ds 2                ; copy of CtlGridY
myCtlGridX          ds 2                ; copy of CtlGridX

FrameHeight         ds 2
FrameWidth          ds 2

knobUL              ds 8                ; This space is used to store the
knobTop             ds 8                ; rectangles that define the grow
knobUR              ds 8                ; knobs on the control. They are
knobRight           ds 8                ; created as needed, and are not
knobLR              ds 8                ; stored in the control record.
knobBottom          ds 8
knobLL              ds 8
knobLeft            ds 8

StdColorTable       dc i1'$C0'          ; grey inact frame, blk normal frame
                    dc i1'$00'          ; black knobs
StdCtlData          dc i2'10,10,200,640,0,0' ; min 10,10; max 200,640; no grid
oldPenState         ds $32              ; storage for old pen state.

GreyPattern         dc i1'$CC,$CC,$CC,$CC,$CC,$CC,$CC,$CC'
                    dc i1'$CC,$CC,$CC,$CC,$CC,$CC,$CC,$CC'
                    dc i1'$CC,$CC,$CC,$CC,$CC,$CC,$CC,$CC'
                    dc i1'$CC,$CC,$CC,$CC,$CC,$CC,$CC,$CC'

                    end

                    EJECT
*******************************************************************************
*
BoxProc             start
*
* Description:      Main Routine for Custom Control.
*
*
* Inputs:           On entry, the parameters are passed to us on the stack.
*
*                   |                   | Previous Contents
*                   |-------------------|
*                   |    ReturnValue    | LONG - Space for return value
*                   |-------------------|
*                   |    CtlCode        | WORD - operation to perform
*                   |-------------------|
*                   |    CtlParam       | LONG - add'l parameter
*                   |-------------------|
*                   |    theCtlHandle   | LONG - Handle to control record
*                   |-------------------|
*                   |    RtnAddr        | 3 BYTES - RTL address
*                   |-------------------|
*                   |                   | <-- Stack pointer
*
* Outputs:          Put something into ReturnValue, pull off the parameters,
*                   and return to the Control Manager.
*
*                   |                   | Previous Contents
*                   |-------------------|
*                   |    ReturnValue    | LONG - Space for return value
*                   |-------------------|
*                   |    RtnAddr        | 3 BYTES - RTL address
*                   |-------------------|
*                   |                   | <-- Stack pointer
*
* External Refs:
*                   Import InitCtlData
*                   Import myDrawCtl
*                   Import myTestCtl
*                   Import myInitCtl
*                   Import myDragCtl
*                   Import myNewValue
*                   Import mySetParams
*                   Import myRecSize
*                   Import null
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

;
; We are going to be using the stack as our direct page for the
; custom control. The equates listed at the beginning of the custom
; control (right after the pageful of comments that explain it)
; describe the layout of the stack frame.
;
; The way we set up the stack frame as our direct page is by first
; saving the old contents of the DP register, transfering the stack
; pointer to the accumulator, and then transferring that to the DP
; register. We also save the contents of the Data Bank Register and
; reset it to the Program Bank Register so that we don't have to
; use 3-byte long addressing anywhere. We will restore both of these
; registers when we leave the custom control.
;

                    pha                 ; push on some room for 'CtlPtr'
                    pha
                    pha                 ; push on some room for 'work'
                    pha

                    phb                 ; save the Data Bank register
                    phd                 ; save the Direct Page register

                    phk                 ; switch data bank to program bank
                    plb
                    tsc                 ; switch Direct Page into stack
                    tcd

                    jsr InitCtlData     ; init some handy data

                    lda <CtlCode        ; get routine # to call
                    cmp #recSize+1      ; we only know of 12 CtlCodes
                    blt ShiftIt
                    lda #6              ; force unknown codes to null events
ShiftIt             ANOP
                    asl a
                    tax
                    jsr (CtlTable,x)

                    sta <ReturnValue    ; save the return value
                    stx <ReturnValue+2

;
; The Return Value has been stored on the stack, and it is time for us to
; return back to the Control Manager. Before we do so, however, we must
; remove the parameters that were passed to us on the stack. We do this
; by moving the RTL address up just below the Return Value, getting the
; stack pointer, and adding an amount to it so that we point to where
; the RTL address has been moved to. We can then simply RTL back to the
; Control Manager, and it will pick up the Return Value right off of the
; stack!
;
                    lda <RtnAddr        ; move the return address up
                    sta <ReturnValue-3
                    lda <RtnAddr+1
                    sta <ReturnValue-2

                    tsc                 ; Get the stack pointer

                    pld                 ; restore caller's Data Bank and
                    plb                 ; Direct Page registers

                    clc                 ; Adjust the stack pointer to point to
                    adc #ReturnValue-4  ; the new location of the RTL address.
                    tcs                 ; Put the stack pointer back.

                    rtl                 ; back to the caller


; This is a table of pointers to routines to be called for specific
; CtlCodes passed to us. The 'null' entries are pointers to a
; routine that does nothing except return 'no error'.

CtlTable            ANOP
                    dc i2'myDrawCtl'    ; 0 drawCtl
                    dc i2'null'         ; 1 calcCRect
                    dc i2'myTestCtl'    ; 2 testCtl
                    dc i2'myInitCtl'    ; 3 initCtl
                    dc i2'null'         ; 4 dispCtl
                    dc i2'null'         ; 5 posCtl
                    dc i2'null'         ; 6 thumbCtl
                    dc i2'myDragCtl'    ; 7 dragCtl
                    dc i2'null'         ; 8 autoTrack
                    dc i2'myNewValue'   ; 9 newValue
                    dc i2'mySetParams'  ;10 setParams
                    dc i2'myInitCtl'    ;11 moveCtl
                    dc i2'myRecSize'    ;12 recSize

                    end

                    EJECT
*******************************************************************************
*
myDrawCtl           start
*
* Description:      Draw control command.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:
*                   Import CalcCorners
*                   Import SetMyPen
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

; figure out the coordinates of the grow knobs.

                    jsr CalcCorners

; Save the pen state, as we'll be changing it a lot.

                    PushLong #oldPenState
                    _GetPenState

; Set the correct width of the pen for the mode we are in.

                    jsr SetMyPen

;
; Set the local variable 'ActivFlag' to indicate how to draw the
; control. We draw it inactive if a) the Hilite value is 255, or b)
; the owner window is inactive and fCtrlTie is set.
;
                    ldy #oCtlHilite     ; are we inactive?
                    lda [<CtlPtr],y
                    and #$00FF
                    cmp #$00FF
                    beq DrawInactive    ; yes, so clear ActivFlag

; At this point, the control can potentially be drawn as an active
; control. But this can only happen if the window is active, or is
; inactive but with the fCtlTie flag clear.

                    ldy #oCtlOwner      ; get the pointer to the control's
                    lda [<CtlPtr],y     ; owning window into 'work'
                    sta <work
                    iny
                    iny
                    lda [<CtlPtr],y
                    sta <work+2

                    ldy #owFrame-4      ; get the frame bits
                    lda [<work],y
                    and #fHilited       ; check the Hilite bit
                    bne DrawActive      ; window is active -> control active
                    lda [<work],y       ; window is inactive
                    and #fCtlTie        ; does CtlTie bit affect control?
                    bne DrawInactive    ; yes, so draw it inactive too

DrawActive          lda #1
                    sta ActivFlag
                    bra SetColorTable

DrawInactive        stz ActivFlag

; Put the color table pointer into a  Direct page location.

SetColorTable       ANOP
                    lda ColorPtr
                    sta <work
                    lda ColorPtr+2
                    sta <work+2

;
; See if the control is active or not, and set the color
; accordingly.
;

                    lda ActivFlag
                    bne SetFrameColor

                    lda [<work]         ; YES - so we are. Use the inactive
                    lsr a               ; pattern for this.
                    lsr a
                    lsr a
                    lsr a
                    and #$000f
                    pha
                    _SetSolidPenPat

                    bra DrawFrame

SetFrameColor       ANOP
                    lda [<work]         ; get the color for the frame from
                    and #$000f          ; the color table and use it.
                    pha
                    _SetSolidPenPat

DrawFrame           ANOP
                    PushLong #myCtlRect ; draw the frame of the control
                    _FrameRect

;
; See if the control is active or not, and set the color accordingly. If
; we so chose we could even decide not to draw knobs on an inactive sizer.
; This is similar to the scrollbar defproc no drawing the thumb on an
; inactive scrollbar.
;

                    lda ActivFlag
                    beq DrawKnobs       ; it's inactive; keep that pattern

                    lda [<work]         ; get the color of the knobs from
                    xba                 ; the color table and set it.
                    and #$000f
                    pha
                    _SetSolidPenPat

DrawKnobs           ANOP
                    ldy #oCtlFlag       ; do we need corner knobs?
                    lda [<CtlPtr],y
                    and #knobCornerMask
                    beq ckEdges         ; no, so check for edge knobs.

                    PushLong #knobUL    ; yes, so draw them
                    _PaintRect

                    PushLong #knobUR
                    _PaintRect

                    PushLong #knobLL
                    _PaintRect

                    PushLong #knobLR
                    _PaintRect

ckEdges             ANOP
                    ldy #oCtlFlag       ; do we need edge knobs?
                    lda [<CtlPtr],y
                    and #knobEdgeMask
                    beq done            ; no, so leave

                    PushLong #knobTop   ; yes, so draw them.
                    _PaintRect

                    PushLong #knobRight
                    _PaintRect

                    PushLong #knobBottom
                    _PaintRect

                    PushLong #knobLeft
                    _PaintRect

done                ANOP
                    PushLong #oldPenState ; clean up after ourselves.
                    _SetPenState

                    lda #0
                    tax

                    rts

ActivFlag           ds 2

                    end

                    EJECT
*******************************************************************************
*
myTestCtl           start
*
* Description:      Hit test command. This routine checks to see if we have
*                   chosen the ability to drag the control when we click in
*                   the interior or not. If so, then we will always return
*                   our partcode (BoxCtlPart = $A0). If not, then we only
*                   return our partcode if we click on the frame or a knob.
*
*
* Inputs:           NONE
*
* Outputs:          A = Part code hit
*
* External Refs:
*                   Import TestFrame
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    jsr TestFrame
                    cmp #noPart         ; hit anything?
                    beq exit            ; no - return nothing
                    lda #BoxCtlPart     ; yes - return our partcode
exit                ANOP
                    ldx #0              ; high byte of return value always $00
                    rts

                    end

                    EJECT
*******************************************************************************
*
myInitCtl           start
*
* Description:      Called to initialize my custom fields of the control
*                   data. It calles mySetParams to handle the data that
*                   is pointed to by the Data/Params field, and makes sure
*                   that the corners of the rectangle lie on the grid.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:
*                   Import mySetParams
*                   Import Snap2Grid
*                   Import SetCtlRect
*                   Import InitCtlData
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

; First, set up some fields based on the value of Param. This is
; a pointer to some additional data, so what SetParams does is
; copy that into our control record.

                    jsr mySetParams
                    jsr InitCtlData

; Next, make sure the edges of the control are aligned to a grid.

                    ldx #^myCtlRect
                    lda #myCtlRect
                    jsr Snap2Grid

                    jsr SetCtlRect

                    lda #0              ; return nothing
                    txa

                    rts
                    end

                    EJECT
*******************************************************************************
*
myDragCtl           start
*
* Description:      Drag command. We hit something. Determine what it was and
*                   drag it around the screen. If we hit the frame, then drag
*                   the whole control. If we hit a knob, then we grow the
*                   control (just like growing a window).
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:
*                   Import SetMyPen
*                   Import Snap2Grid
*                   Import myDrawCtl
*                   Import FindPart
*                   Import SetCtlRect
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    PushLong #oldPenState ; save drawing state, as we change it
                    _GetPenState

                    ldy #6              ; make a working copy of CtlRect.
loop100             lda myCtlRect,y
                    sta drag_Rect,y
                    dey
                    dey
                    bpl loop100

                    pha                 ; has the mouse been lifted up?
                    PushWord #mUpMask   ; ask for any such events
                    PushLong #TrackEvent
                    _GetNextEvent
                    pla

                    lda TrackEvent+oWhat
                    cmp #mouseUpEvt     ; was there a mouse up event?
                    bne mDown           ; no - so start tracking
                    brl done            ; yes - so leave

mDown               ANOP
                    PushLong #TrackEvent+oWhere
                    _GlobalToLocal

                    lda TrackEvent+oWhere
                    sta OldMouse        ; FindPart likes the mouse Position
                    sta theParam        ; in 'theParam'
                    lda TrackEvent+oWhere+2
                    sta OldMouse+2
                    sta theParam+2

                    jsr FindPart        ; what did we hit? (returns internal
                    sta dragPart        ;   partcode number).

                    cmp #InteriorPart+1 ; what part did we hit?
                    bge GrowFrame       ; some knob - grow the control
                    brl DragFrame       ; interior or frame - move the control

;
; This part of the program is called when we detect that we have clicked on
; a grow knob. It tracks the motions of the mouse, and grows/draws the
; control appropriately.
;
GrowFrame           ANOP
                    PushWord #2         ; set us to XOR mode. This is for the
                    _SetPenMode         ; rubber-banding effect of growing.

                    jsr SetMyPen        ; set my pen's width

                    PushLong #GreyPattern ; grow with a grey pattern
                    _SetPenPat

                    ldy #oCtlMaxX       ; get local copies of the maximum
                    ldx #6              ; and minimum dimensions.
loop0010            lda [<CtlPtr],y
                    sta MinY,x
                    dey
                    dey
                    dex
                    dex
                    bpl loop0010

; validate the min and max values. the min values must be at least $10.
; if not, they are set there. the max values must be at least as large
; as the min values. if not, they are set to $FFFF

                    lda #$10
                    cmp MinX
                    blt CkMinY
                    sta MinX

ckMinY              cmp MinY
                    blt CkMaxX
                    sta MinY

ckMaxX              lda MaxX
                    cmp MinX
                    bge ckMaxY
ChgX                lda #$FFFF
                    sta MaxX

ckMaxY              lda MaxY
                    cmp MinY
                    bge doneCk
ChgY                lda #$FFFF
                    sta MaxY
doneCk              ANOP

;
; We grow the control with the following algorithm. First, setup by:
;
;  1. saving the current mouse postion in 'NewMouse',
;  2. aligning 'NewMouse' to the grid, and
;  3. making a working copy of the control's rectangle in 'drag_rect'.
;
; We then enter the main loop:
;
;  4. Draw a copy of the rubber-banding rectangle.
;  5. Check the mouse button. If it is up, goto #12
;  6. Put the position of the mouse into 'NewerMouse'. Align it to grid.
;  7. Check it against 'NewMouse'. If it hasn't changed, goto #5.
;  8. Erase the old rubber-Band.
;  9. Calculate the new rectangle and put it into 'drag_rect'
; 10. Move 'NewerMouse' into 'NewMouse'
; 11. Goto #4
;
; The growing is all done. Clean up and leave.
;
; 12. Erase the rubber band.
; 13. Erase the control.
; 14. Invalidate the area under the control
; 15. Make drag_rect the new boundary of the control
; 16. Say goodnite, Gracie.
;

                    lda oldMouse
                    sta NewMouse
                    lda oldMouse+2
                    sta NewMouse+2

                    ldx #^NewMouse      ; make sure we are on the grid!!!
                    lda #NewMouse
                    jsr Snap2Grid

DrawLoop            ANOP
                    PushLong #drag_Rect ; draw the rubber-band
                    _FrameRect

WaitLoop            ANOP
                    pha                 ; has the mouse been lifted up?
                    PushWord #mUpMask   ; ask for any such events
                    PushLong #TrackEvent
                    _GetNextEvent
                    pla

                    lda TrackEvent+oWhat
                    cmp #mouseUpEvt
                    beq done            ; mouse up occured - so leave.
                    
                    lda TrackEvent+oWhere ; mouse still down. copy it to
                    sta NewerMouse      ; NewerMouse
                    lda TrackEvent+oWhere+2
                    sta NewerMouse+2
                    
                    PushLong #NewerMouse ; convert it to window coordinates.
                    _GlobalToLocal

                    ldx #^NewerMouse    ; make sure the new position is on
                    lda #NewerMouse     ; the grid.
                    jsr Snap2Grid

                    lda NewerMouse      ; See if the mouse position moved. We
                    cmp NewMouse        ; only redraw the rubber-band if it did.
                    bne ReDraw          ; It did - redraw the rubber-band
                    lda NewerMouse+2
                    cmp NewMouse+2
                    beq WaitLoop        ; No movement - check button state

ReDraw              ANOP
                    PushLong #drag_Rect ; erase the old rubber band
                    _FrameRect

                    lda NewerMouse      ; copy NewerMouse for next loop
                    sta NewMouse
                    sec                 ; and update deltaXY for AdjFrameRect
                    sbc OldMouse
                    sta deltaY

                    lda NewerMouse+2
                    sta NewMouse+2
                    sec
                    sbc OldMouse+2
                    sta deltaX

                    jsr AdjFrameRect    ; adjust for drawing the new rect

                    brl DrawLoop        ; redraw the rubber-band

done                ANOP
                    PushLong #drag_Rect ; erase the rubber band
                    _FrameRect

                    PushLong #myCtlRect ; erase the control
                    _EraseRect

                    PushLong #myCtlRect ; invalidate the stuff under it
                    _InvalRect

                    brl Exit
;
; This part of the program is called when we have clicked on either the
; frame or the interior. It calls dragrect to move an outline of the
; control around.
;

DragFrame           ANOP

; Get the portRect of the window that owns this control. This rectangle
; will be used to control the limits of dragging.

                    ldy #oCtlOwner      ; get the pointer to the control's
                    lda [<CtlPtr],y     ; owning window into 'work'
                    sta <work
                    iny
                    iny
                    lda [<CtlPtr],y
                    sta <work+2

                    ldy #oPortRect+6    ; copy the PortRect of the window
                    ldx #6              ; out of its GrafPort into limitRect.
loop400             lda [<work],y
                    sta limitRect,x
                    dey
                    dey
                    dex
                    dex
                    bpl loop400

;
; Use dragRect to drag an outline of the control around the
; screen for me.
;
                    pha                 ; space for result
                    pha
                    PushLong #DragDraw  ; no action proc
                    PushLong #GreyPattern ; drag pattern
                    PushLong OldMouse   ; starting location of mouse
                    PushLong #drag_rect ; outline to drag
                    PushLong #limitRect ; limitRect
                    PushLong #limitRect ; slopRect
                    PushWord #%00101000 ; custom drag flag, ReturnRect flag
                    _DragRect

                    pla                 ; pull off and discard the deltas
                    pla

                    PushLong #myCtlRect ; erase the control in its old pos'n
                    _EraseRect

                    PushLong #myCtlRect ; invalidate the stuff under it
                    _InvalRect

                    ldx #^drag_Rect     ; align the new rectangle to the grid.
                    lda #drag_Rect
                    jsr Snap2Grid

Exit                ANOP
                    ldy #6              ; copy over the new control frame
loop200             lda drag_Rect,y
                    sta myCtlRect,y
                    dey
                    dey
                    bpl loop200

                    PushLong #myCtlRect ; Invalidate the new area under the
                    _InvalRect          ; control so it will be redrawn.

                    PushLong #oldPenState
                    _SetPenState        ; clean up QuickDraw after ourselves.

                    jsr SetCtlRect      ; copy new rect into control record

                    lda #-1             ; Say that we handled it
                    tax

                    rts

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Called by DragRect to draw the rectangle of the control. I draw the outline
; myself so that I can align it to my own grid. The normal aligning of the
; rectangle to the grid only aligns to powers of 2. I align to anything.
;
; When this routine is called, the stack looks like this:
;
;                   PUSH:WORD - delta X
;                   PUSH:WORD - delta Y
;                   PUSH:BYTE[3] - return address
;
; However, since I set the ReturnRect flag, I can ignore the deltas passed to
; me, and use drag_rect directly. I make a copy of it, make sure that it is
; aligned to the grid, draw it, remove the parameters, and return to the
; control manager.
;

DragDraw            ANOP

RTLAddr             equ 1
DY                  equ RTLAddr+3
DX                  equ DY+2

                    ldx #6              ; make a copy of the rectangle that
loop500             lda drag_rect,x     ; _DragRect is passing to us. We are
                    sta draw_rect,x     ; going to modify it so that it is
                    dex                 ; aligned to the grid.
                    dex
                    bpl loop500

                    ldx #^draw_Rect     ; align the new rectangle to the grid.
                    lda #draw_Rect
                    jsr Snap2Grid

DrawOutline         ANOP
                    PushLong #draw_rect
                    _FrameRect

ExitDragDraw        ANOP
                    lda 1,s             ; move the RTL address back up
                    sta 5,s
                    lda 2,s
                    sta 6,s

                    pla                 ; remove the parameters (DX, DY)
                    pla

                    rtl                 ; back to dragRect!!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Called by the routine that grows the control with a rubber-band
; sort of thing. It uses the part code we hit as an index into
; a table. This table contains a list of offsets into the
; 4 words of drag_rect. It reads in a pair of these offsets and
; uses them to modify the appropriate X/Y coordinates with
; deltaY and deltaX. DeltaY and deltaX are set up by the
; rubber-banding routine.

AdjFrameRect        ANOP
                    ldx #6
loop380             lda drag_rect,x
                    sta new_rect,x
                    dex
                    dex
                    bpl loop380

                    lda dragPart        ; get the part code we are dragging
                    sec
                    sbc #ULPart
                    asl a               ; turn it into an index.
                    asl a
                    tax
                    lda PtToAdj,x       ; get Y coordinate to adjust
                    tay
                    lda PtToAdj+2,x     ; get X coordinate to adjust
                    tax

; if the entry that was fetched from the table was -1, then that means
; that we are making no adjustments in that direction. For instance, it
; we are dragging on the top edge knob, we want to adjust just the top
; side of the rectangle, but want to leave either of the sides alone.

                    cpy #-1             ; modify the vertical coordinate
                    beq NoChg1          ; of choice if necessary.
                    clc
                    lda deltaY
                    adc myCtlRect,y
                    sta new_rect,y

NoChg1              cpx #-1             ; modify the horizontal coordinate
                    beq NoChg2          ; of choice if necessary.
                    clc
                    lda deltaX
                    adc myCtlRect,x
                    sta new_rect,x

NoChg2              ANOP
                    ldx #^new_rect      ; align the rectangle to the grid.
                    lda #new_rect
                    jsr Snap2Grid

; now that we have a new rectangle, we have to make sure that it is within
; the maximum and minimum sizes allowed.

                    sec                 ; make sure the new Y size is OK
                    lda new_rect+4
                    sbc new_rect
                    bcc NoCopy1
                    cmp MinY
                    blt NoCopy1         ; it's not, so leave old coords there
                    inc a
                    cmp MaxY
                    bge NoCopy1         ; it's not, so leave old coords there

                    lda new_rect        ; it is, so update it.
                    sta drag_rect
                    lda new_rect+4
                    sta drag_rect+4

NoCopy1             ANOP
                    sec                 ; make sure the new X size is OK
                    lda new_rect+6
                    sbc new_rect+2
                    bcc NoCopy2
                    cmp MinX
                    blt NoCopy2         ; it's not, so leave old coords there
                    inc a
                    cmp MaxX
                    bge NoCopy2         ; it's not, so leave old coords there

                    lda new_rect+2      ; it is, so update it
                    sta drag_rect+2
                    lda new_rect+6
                    sta drag_rect+6
NoCopy2             ANOP
                    rts


PtToAdj             dc i2'0,2,0,-1,0,6,-1,6,4,6,4,-1,4,2,-1,2'

TrackEvent          ds $10

OldMouse            ds 4
NewMouse            ds 8
NewerMouse          ds 8
drag_Rect           ds 8
draw_Rect           ds 8
new_Rect            ds 8
limitRect           ds 8
MinY                ds 2
MinX                ds 2
MaxY                ds 2
MaxX                ds 2

                    end
